using System;
using Microsoft.Xna.Framework;

namespace XDL.Framework.Particles
{
    /// <summary>
    /// Base helper for objects that want to leave particles behind them as they
    /// move around the world. This emitter implementation solves two related
    /// problems:
    /// 
    /// If an object wants to create particles very slowly, less than once per
    /// frame, it can be a pain to keep track of which updates ought to create
    /// a new particle versus which should not.
    /// 
    /// If an object is moving quickly and is creating many particles per frame,
    /// it will look ugly if these particles are all bunched up together. Much
    /// better if they can be spread out along a line between where the object
    /// is now and where it was on the previous frame. This is particularly
    /// important for leaving trails behind fast moving objects such as rockets.
    /// 
    /// This emitter class keeps track of a moving object, remembering its
    /// previous position so it can calculate the velocity of the object. It
    /// works out the perfect locations for creating particles at any frequency
    /// you specify, regardless of whether this is faster or slower than the
    /// game update rate.
    /// </summary>
    public abstract class Emitter
    {
        #region Fields
        float m_timeBetweenParticles;
        float m_timeLeftOver;
        Vector3 m_previousPosition;
        Vector3 m_previousVelocity;
        #endregion


        /// <summary>
        /// Constructs a new particle emitter object.
        /// </summary>
        public Emitter(float particlesPerSecond)
        {
            m_timeBetweenParticles = 1.0f / particlesPerSecond;
        }

        /// <summary>
        /// Constructs a new particle emitter object.
        /// </summary>
        public Emitter(float particlesPerSecond, Vector3 currentPosition, Vector3 currentVelocity)
        {
            m_timeBetweenParticles = 1.0f / particlesPerSecond;
            m_previousPosition = currentPosition;
            m_previousVelocity = currentVelocity;
        }

        /// <summary>
        /// Start the particle emitter.
        /// </summary>
        /// <param name="position">New position</param>
        public virtual void Start(Vector3 position, Vector3 velocity)
        {
            m_timeLeftOver = 0;
            m_previousPosition = position;
            m_previousVelocity = velocity;
        }
       
        /// <summary>
        /// Actually adds a particle in the underlying particle system.
        /// </summary>
        /// <param name="position">Position to emit the particle with.</param>
        /// <param name="velocity">Initial velocity of the particle.</param>
        protected abstract void AddParticle(Vector3 position, Vector3 velocity);

        /// <summary>
        /// Updates the emitter, creating the appropriate number of particles
        /// in the appropriate positions.
        /// </summary>
        /// <param name="gameTime">Current game time</param>
        /// <param name="newPosition">New emitter position</param>
        /// <param name="velocity">New initial velocity</param>
        public void Update(GameTime gameTime, Vector3 newPosition, Vector3 newVelocity)
        {
            // Work out how much time has passed since the previous update.
            float elapsedTime = (float)gameTime.ElapsedGameTime.TotalSeconds;

            if (elapsedTime > 0)
            {
                // If we had any time left over that we didn't use during the
                // previous update, add that to the current elapsed time.
                float timeToSpend = m_timeLeftOver + elapsedTime;
                
                // Counter for looping over the time interval.
                float currentTime = -m_timeLeftOver;

                // Create particles as long as we have a big enough time interval.
                while (timeToSpend > m_timeBetweenParticles)
                {
                    currentTime += m_timeBetweenParticles;
                    timeToSpend -= m_timeBetweenParticles;

                    // Work out the optimal position for this particle. This will produce
                    // evenly spaced particles regardless of the object speed, particle
                    // creation frequency, or game update rate.
                    float mu = currentTime / elapsedTime;

                    Vector3 position = Vector3.Lerp(m_previousPosition, newPosition, mu);
                    Vector3 velocity = Vector3.Lerp(m_previousVelocity, newVelocity, mu);

                    AddParticle(position,velocity);
                }

                // Store any time we didn't use, so it can be part of the next update.
                m_timeLeftOver = timeToSpend;
            }
            m_previousPosition = newPosition;
            m_previousVelocity = newVelocity;
        }
    }
}
